using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.IO;
using System.Text.RegularExpressions;

namespace GeneratorLibrary.ClassGeneratorLibrary {
    class StringCodeBuilder {
        int tabs = 0;
        StringBuilder sb = new StringBuilder();
        public void Append(string value) {
            string[] values = value.Split(new char[] { '\n' });
            for (int i = 0; i < values.Length - 1; i++) {
                AddTabsAndAppend(values[i]);
                sb.Append('\n');
            }
            AddTabsAndAppend(values[values.Length - 1]);
        }
        private void Append(string value, bool fromNewLine) {
            string[] values = value.Split(new char[] { '\n' });
            for (int i = 0; i < values.Length - 1; i++) {
                if (fromNewLine)
                    AddTabsAndAppend(values[i]);
                else
                    sb.Append(values[i]);
                sb.Append('\n');
                fromNewLine = true;
            }
            if (fromNewLine)
                AddTabsAndAppend(values[values.Length - 1]);
            else
                sb.Append(values[values.Length - 1]);
            fromNewLine = true;
        }

        private void AddTabsAndAppend(string s) {
            if (s.Length > 0) {
                for (int i = 0; i < tabs; i++)
                    sb.Append('\t');
                sb.Append(s);
            }
        }
        public void AppendInLine(string value) {
            sb.Append(value);
        }
        public void AppendBeginAfter(string value) {
            Append(value);
            AddTab();
        }
        public void AppendEndBefore(string value) {
            RemoveTab();
            Append(value);
        }
        public void AddTab() {
            tabs++;
        }
        public void RemoveTab() {
            if (tabs > 0)
                tabs--;
        }

        public void AppendBraces(string value) {
            bool fromNewLine = true;
            int i = 0;
            char[] delims = new char[] { '{', '}', '\n' };
            int next = value.IndexOfAny(delims);
            while (i <= next) {                
                switch (value[next]) {
                    case '{':
                        Append(value.Substring(i, next - i + 1), fromNewLine);
                        fromNewLine = false;
                        AddTab();
                        break;
                    case '}':
                        RemoveTab();
                        Append(value.Substring(i, next - i + 1), fromNewLine);
                        fromNewLine = false;
                        break;
                    default://line break
                        Append(value.Substring(i, next - i + 1), fromNewLine);
                        fromNewLine = true;
                        break;
                }
                i = next + 1;
                next = value.IndexOfAny(delims, next + 1);
            }
            if (i < value.Length) {
                Append(value.Substring(i) + "\n", fromNewLine);
            }
        }
        public override string ToString() {
            return sb.ToString();
        }
    }

    public class DataClass {
        List<Column> columns;
        StringCodeBuilder classBuilder;
        public DataClass(DataTable dt, string table, string classNamespace, string dacNamespace) {
            columns = new List<Column>(dt.Rows.Count);
            foreach (DataRow row in dt.Rows) {
                columns.Add(new Column(row));
            }
            generate(classNamespace, dacNamespace, table);
        }

        private void generate(string classNamespace, string dacNamespace, string table) {
            classBuilder = new StringCodeBuilder();

            GenerateUsingStatements(classNamespace, dacNamespace);
            classBuilder.Append("\n");
            GenerateNamespace(classNamespace, table);
        }


        private void GenerateUsingStatements(string classNamespace, string dacNamespace) {
            classBuilder.Append("using System;\nusing System.Collections.Generic;\nusing System.Data;\nusing System.Data.SqlClient;\nusing StandardDAC;\nusing StandardDAC.SqlClient;\n");
            if (classNamespace != dacNamespace && !String.IsNullOrEmpty(dacNamespace))
                classBuilder.Append("using " + dacNamespace + ";\n");

            classBuilder.Append("\n");
        }
        private void GenerateNamespace(string classNamespace, string table) {
            if (!String.IsNullOrEmpty(classNamespace))
                classBuilder.AppendBeginAfter("namespace " + classNamespace + " {\n");

            GenerateClass(classNamespace, table);

            if (!String.IsNullOrEmpty(classNamespace))
                classBuilder.AppendEndBefore("}\n");
        }

        private void GenerateClass(string classNamespace, string table) {
            classBuilder.AppendBeginAfter("public class " + table + " : IDataModel {\n");

            GenerateMembers();
            GenerateProperties();
            GenerateConstructors(table);
            GenerateDACMethods(table);

            classBuilder.AppendEndBefore("}\n");
        }
        private void GenerateDACMethods(string table) {
            classBuilder.Append("#region DAC Methods\n");
            classBuilder.Append("/// <summary>\n///Reads all items in database.\n/// </summary>\n");
            classBuilder.AppendBeginAfter("public static List<" + table + "> ReadAll() {\n");
            classBuilder.Append("return new DAC().ReadAll<" + table + ">(\"" + table + "\");\n");
            classBuilder.AppendEndBefore("}\n");

            classBuilder.Append("/// <summary>\n///Populates item from database by its id.\n/// </summary>\n");
            classBuilder.AppendBeginAfter("private void populate() {\n");
            classBuilder.Append("new DAC().ReadById<" + table + ">(this, \"" + table + "\");\n");
            classBuilder.AppendEndBefore("}\n");

            classBuilder.Append("/// <summary>\n///Saves item to database.\n/// </summary>\n");
            classBuilder.AppendBeginAfter("public void Save() {\n");
            classBuilder.Append("new DAC().Save(this, \"" + table + "\");\n");
            classBuilder.AppendEndBefore("}\n");

            classBuilder.Append("/// <summary>\n///Deletes item from database.\n/// </summary>\n");
            classBuilder.AppendBeginAfter("public void Delete() {\n");
            classBuilder.Append("new DAC().Delete(this, \"" + table + "\");\n");
            classBuilder.AppendEndBefore("}\n");

            foreach (Column c in columns) {
                if (c.IsForeignKey || (!c.IsPrimaryKey && c.IsUniqueConstraint)) {
                    string comment = c.IsForeignKey ? "Read By Foreign Key" : "Read by Unique Constraint";

                    if (c.DataType == "bool?") {
                        classBuilder.Append("/// <summary>\n///" + comment + "\n/// </summary>\n/// <param name=\"" + c.ColumnName + "\">0: FALSE, 1: TRUE, 2:NULL</param>\n");
                        classBuilder.AppendBeginAfter("public static List<" + table + "> ReadBy" + c.ColumnName + "(" + c.DataType + " in" + c.ColumnName + ") {");
                        classBuilder.Append("object parameter = in" + c.ColumnName + ".HasValue ? (in" + c.ColumnName + ".Value ? 1 : 0) : 2);\n");
                        classBuilder.Append("return new DAC().ReadByParams<" + table + ">(\"" + table + "\", new SqlParameter(\"@" + c.ColumnName + "\", parameter));");
                        classBuilder.AppendEndBefore("}\n");
                    } else if (c.IsNullable) {
                        classBuilder.Append("/// <summary>\n///" + comment + "\n/// </summary>\n/// <param name=\"" + c.ColumnName + "\"></param>\n");
                        classBuilder.AppendBeginAfter("public static List<" + table + "> ReadBy" + c.ColumnName + "(" + c.DataType + " in" + c.ColumnName + ") {\n");

                        if (c.NullValue == "null") {
                            classBuilder.Append("//object parameter = in" + c.ColumnName + " ?? " + c.NullValue + ";\n");
                            classBuilder.Append("return new DAC().ReadByParams<" + table + ">(\"" + table + "\", new SqlParameter(\"@" + c.ColumnName + "\", in" + c.ColumnName + "));\n");
                        } else {
                            classBuilder.Append("object parameter = in" + c.ColumnName + " ?? " + c.NullValue + ";\n");
                            classBuilder.Append("return new DAC().ReadByParams<" + table + ">(\"" + table + "\", new SqlParameter(\"@" + c.ColumnName + "\", parameter));");
                        }
                        
                        classBuilder.AppendEndBefore("}\n");
                    } else {
                        classBuilder.Append("/// <summary>\n///" + comment + "\n/// </summary>\n/// <param name=\"" + c.ColumnName + "\"></param>\n");
                        classBuilder.AppendBeginAfter("public static List<" + table + "> ReadBy" + c.ColumnName + "(" + c.DataType + " in" + c.ColumnName + ") {\n");
                        classBuilder.Append("return new DAC().ReadByParams<" + table + ">(\"" + table + "\", new SqlParameter(\"@" + c.ColumnName + "\", in" + c.ColumnName + "));\n");
                        classBuilder.AppendEndBefore("}\n");
                    }
                }
            }
            classBuilder.Append("#endregion DAC Methods\n");
        }

        private void GenerateConstructors(string table) {
            classBuilder.Append("#region Constructors\n");

            //default constructor
            classBuilder.Append("/// <summary>\n///Default Constructor. Required by DAL Helper\n/// </summary>\n");
            classBuilder.Append("public " + table + "() { }\n");

            //populateById constructor
            classBuilder.Append("/// <summary>\n///Gets item by Id.\n/// </summary>\n/// <param name=\"inId\"></param>\n");
            classBuilder.AppendBeginAfter("public " + table + "(int inId) {\n");
            classBuilder.Append("this.id = inId;\npopulate();\n");
            classBuilder.AppendEndBefore("}\n");
            classBuilder.Append("\n");

            classBuilder.Append("#endregion Constructors\n");
        }

        private void GenerateProperties() {
            List<string> propertyMethods = new List<string>();
            classBuilder.Append("#region Properties\n");
            foreach (Column c in columns) {
                classBuilder.AppendBraces(c.DacAttribute);
                classBuilder.AppendBraces(c.Property);
                if (!String.IsNullOrEmpty(c.ExtraMethod))
                    propertyMethods.Add(c.ExtraMethod);
            }
            classBuilder.Append("\n#endregion Properties\n");

            if (propertyMethods.Count > 0) {
                classBuilder.Append("#region IsNew()\n");
                foreach (string s in propertyMethods) {
                    classBuilder.AppendBraces(s);
                }
                classBuilder.Append("\n#endregion IsNew()\n");
            }
        }

        private void GenerateMembers() {
            classBuilder.Append("#region members\n");
            foreach (Column c in columns) {
                classBuilder.Append(c.Member);
            }
            classBuilder.Append("\n#endregion members\n");
        }
        public void WriteNewFile(string filepath){
            FileInfo f = new FileInfo(filepath);
            if (!f.Exists || f.Length == 0){
                using (StreamWriter w = new StreamWriter(filepath)){
                    w.Write(classBuilder.ToString().Replace("\n", Environment.NewLine));
                }
            }
        }
        public string WriteString() {
            return classBuilder.ToString();
        }
    }
}
